home *** CD-ROM | disk | FTP | other *** search
/ Visual Basic Source Code / Visual Basic Source Code.iso / vbsource / metkit / setup.exe / EXAMPLES / FTPCAT / FTPCAT.CPP next >
C/C++ Source or Header  |  1997-06-08  |  8KB  |  285 lines

  1. //    Copyright (C) 1996, 1997 Meta Four Software.  All rights reserved.
  2. //
  3. //    Ftp site catalog sample code
  4. //
  5. //! rev="$Id: ftpcat.cpp,v 1.3 1997/05/27 00:06:40 jcw Rel $"
  6.  
  7. #include "m4kit.h"
  8.  
  9. #include <io.h>
  10.  
  11. /////////////////////////////////////////////////////////////////////////////
  12. // Property definitions
  13.  
  14.     c4_ViewProp        pFiles ("files");
  15.     c4_IntProp        pParent ("parent"),
  16.                     pSize ("size"),
  17.                     pDate ("date");
  18.     c4_StringProp    pName ("name");
  19.  
  20. /////////////////////////////////////////////////////////////////////////////
  21. // Reconstruct the full path name from a subdirectory index in the tree
  22.  
  23. CString fFullPath(c4_View& dirs_, int dirNum_)
  24. {
  25.         // Prefix all parent dir names until the root level is reached
  26.     CString path;
  27.     for (;;)
  28.     {
  29.         path = pName (dirs_[dirNum_]) + "/" + path;
  30.  
  31.         if (dirNum_ == 0)
  32.             return path; // this result always has a trailing backslash
  33.             
  34.         dirNum_ = (int) pParent (dirs_[dirNum_]);
  35.     }
  36. }
  37.  
  38. /////////////////////////////////////////////////////////////////////////////
  39. // Attempt to convert Unix date back to good ol' DOS format, i.e. 7:5:4 bits
  40.  
  41. int DecodeUnixDate(const CString& buf_)
  42. {
  43.     static CString months = "JanFebMarAprMayJunJulAugSepOctNovDec";
  44.  
  45.         // extra logic, in case no year was specified
  46.     static int limit = 0, year = 0;
  47.     if (!limit)
  48.     {
  49.             // determine the time 30 days ahead (clock may be a bit off)
  50.         time_t t1 = time(0) + 30 * 24 * 3600L;
  51.         tm* t2 = gmtime(&t1);
  52.             // remember that date as the limit date
  53.         limit = ((t2->tm_year - 80) << 9) |
  54.                 ((t2->tm_mon + 1) << 5) | t2->tm_mday;
  55.             // remember the default year to use if none is specified
  56.             // this may be a year to far, we'll back up later if needed
  57.         year = t2->tm_year + 1900;
  58.     }
  59.  
  60.     int m = months.Find(buf_.Left(3));
  61.     if (m % 3 != 0 || buf_[3] != ' ' || buf_[6] != ' ')
  62.         return 0;
  63.  
  64.     int y = buf_[9] == ':' ? year : atoi(buf_.Mid(8,4));
  65.     if (y < 1980 || y > 2080)
  66.         return 0;
  67.  
  68.     int x = ((y - 1980) << 9) | ((m / 3 + 1) << 5) | atoi(buf_.Mid(4,2));
  69.         // if the date is within a year into the future, use prev year
  70.     if (x > limit && x - 512 < limit)
  71.         x -= 512;
  72.     return x;
  73. }
  74.  
  75. /////////////////////////////////////////////////////////////////////////////
  76. // Decode a single line of a Unix-style directory listing
  77.  
  78.     // Note: there are a *lot* more systems and listing formats out there,
  79.     // but this is only an example, it works fine with several popular sites
  80.  
  81. char DecodeUnixEntry(const CString& buf_, c4_Row& entry_)
  82. {
  83.         // assume each entry ends with: ' ' <size> ' ' <date> ' ' <name>
  84.     int n = buf_.ReverseFind(' ');
  85.  
  86.         // Mac filenames can contain ' ', so check in regular place first
  87.     if (n > 55 && buf_[54] == ' ' && buf_[41] == ' ' &&
  88.                         DecodeUnixDate(buf_.Mid(42, 12)) != 0)
  89.         n = 54; // date in regular position is ok, so use that instead
  90.  
  91.     if (n > 40 && buf_[n-13] == ' ')
  92.     {
  93.         pName (entry_) = buf_.Mid(n+1);
  94.  
  95.         switch (buf_[0])
  96.         {
  97.         case '-':        // regular file
  98.                     pDate (entry_) = DecodeUnixDate(buf_.Mid(n-12, 12));
  99.                     n = buf_.Left(n-13).ReverseFind(' ');
  100.                     if (n > 20)
  101.                     {
  102.                         pSize (entry_) = atol(buf_.Mid(n));
  103.                         return 'f';
  104.                     }
  105.                     break;
  106.  
  107.         case 'd':        // directory, but not if name starts with '.'
  108.                     if (buf_[n+1] != '.')
  109.                         return 'd';
  110.         }
  111.     }
  112.  
  113.     return 0;
  114. }
  115.  
  116. /////////////////////////////////////////////////////////////////////////////
  117. // Scan a remote ftp site and return a corresponding structure for it
  118. //
  119. //    This code is extremely simple in terms of tcp/ip stuff, since there
  120. //    aren't any calls - everything is handled by Win95's FTP.EXE program.
  121. //
  122. //    On the other hand, this code is pretty tricky since two pipes are
  123. //    set up to control the execution of this child program. One pipe feeds
  124. //    commands to ftp, the other reads back results and decodes each line.
  125. //
  126. //    For some unknown reason, all server messages are lost. This is not
  127. //    critical, since the list output *does* get through, as well as any
  128. //    other output from commands. That's enough to make this thing work.
  129. //
  130. //    The bottom line is:
  131. //
  132. //        1)  There is no networking stuff in here, FTP.EXE does it all
  133. //        2)    This is built as a console task, so popen is neatly hidden
  134. //        3)    The result is a perfectly usable command-line utility
  135. //        4)    This only works on Win95 (perhaps also on NT 3.51 or 4.0)
  136. //        5)    The port suffix (site::port) is not implemented
  137.  
  138. c4_View fScanFTP(const char* path_, const char* usr_, const char* pw_)
  139. {
  140.         // everything before the first '/' is the site name
  141.     CString site = path_;
  142.     site = site.SpanExcluding("/");
  143.  
  144.         // start with a view containing the path (without the site prefix)
  145.     c4_View dirs;
  146.     dirs.Add(pName [path_ + site.GetLength()]);
  147.  
  148.         // set up a pipe, this reminds me of the good ol' Unix days...
  149.     int ph[2];
  150.     VERIFY(_pipe(ph, 100000, 0) == 0); // allow 100k in the pipeline
  151.     VERIFY(_dup2(ph[1], 1) == 0);
  152.     VERIFY(_close(ph[1]) == 0);
  153.         // now, when the "list" file is read, its data will come from stdout
  154.     FILE* list = fdopen(ph[0], "rt");
  155.     ASSERT(list);
  156.  
  157.     fprintf(stderr, "Connecting to %s ...\n", (const char*) site);
  158.  
  159.         // spawn a process, connect, and prepare to pipe commands to it
  160.     FILE* cmds = _popen("ftp -n " + site, "wt");
  161.     ASSERT(cmds);
  162.  
  163.         // only the child should have the write side of the pipe open
  164.     VERIFY(_dup2(2, 1) == 0);
  165.  
  166.         // prepare to do some work by logging in
  167.     fprintf(cmds, "user %s %s\npwd\n", usr_, pw_);
  168.     fflush(cmds);
  169.  
  170.         // read one line back right now, to make sure connection is ok
  171.     char buf [1024];
  172.     fgets(buf, sizeof buf, list);
  173.  
  174.         // This loop "automagically" handles the recursive traversal of all
  175.         // subdirectories. The trick is that each scan may add new entries
  176.         // at the end, causing this loop to continue (GetSize() changes!).
  177.     
  178.     int i;
  179.  
  180.     for (i = 0; i < dirs.GetSize(); ++i)
  181.     {
  182.         CString path = fFullPath(dirs, i);
  183.         if (path != "/")    // remove the trailing slash
  184.             path = path.Left(path.GetLength() - 1);
  185.  
  186.             // send two command to the child process, the second one is
  187.             // needed to produce a trailing line we can wait on
  188.         fprintf(stderr, "%4d: %-65.65s\r", i, (const char*) path);
  189.         fputs("dir " + path + "\npwd\n", cmds);
  190.         fflush(cmds);
  191.  
  192.         c4_View files;
  193.         c4_Row dir, file;
  194.  
  195.             // look at each of the returned lines in turn
  196.         while (fgets(buf, sizeof buf, list))
  197.         {
  198.             CString temp = buf;
  199.             temp = temp.SpanExcluding("\r\n");
  200.  
  201.             int result = atoi(temp);
  202.             if (result == 0)
  203.             {
  204. //                puts(temp);
  205.                 char type = DecodeUnixEntry(temp, file);
  206.                 if (type == 'd')
  207.                 {
  208.                     pParent (dir) = i;
  209.                     pName (dir) = pName (file);
  210.                     dirs.Add(dir);
  211.                 }
  212.                 else if (type == 'f')
  213.                 {
  214.                     files.Add(file);
  215.                 }
  216.             }
  217.             else
  218.             {
  219.                 pFiles (dirs[i]) = files.SortOn(pName);
  220.                 break;
  221.             }
  222.         }
  223.     }
  224.  
  225.     fprintf(stderr, "%75s\r%4d directories scanned.\n", "", i);
  226.  
  227.     _pclose(cmds);
  228.     fclose(list);
  229.     
  230.         // The returned object contains the entire directory tree.
  231.         // Everything is automatically destroyed when no longer referenced.    
  232.     return dirs;
  233. }
  234.  
  235. /////////////////////////////////////////////////////////////////////////////
  236. // Try this on internet: "ftpcat ftp.winsite.com/pub/pc/win95/programr"
  237.  
  238. int main(int argc, char** argv)
  239. {
  240.     const char* dest = argc == 3 ? argv[--argc] : "ftpcat.dat";
  241.  
  242.     if (argc != 2)
  243.     {
  244.         fprintf(stderr, "Usage: FTPCAT site/path [output]\n"
  245.             "   or: FTPCAT [ftp://][user:passwd@]site[/path] [output]\n");
  246.         return 1;
  247.     }
  248.  
  249.         // the following logic splits up an URL into its components
  250.  
  251.     CString arg = argv[1];
  252.     if (arg.Left(6).CompareNoCase("ftp://") == 0)
  253.         arg = arg.Mid(6);
  254.  
  255.     CString id = "anonymous:ftpcat@any.org";
  256.     if (arg.Find('@') >= 0)
  257.     {
  258.         int n = arg.ReverseFind('@'); // use the last one!
  259.         id = arg.Left(n);
  260.         arg = arg.Mid(n + 1);
  261.     }
  262.  
  263.     CString pass;
  264.     if (id.Find(':') >= 0)
  265.     {
  266.         int n = id.Find(':');
  267.         pass = id.Mid(n + 1);
  268.         id = id.Left(n);
  269.     }
  270.  
  271.         // ready to scan, prepare a storage object for the results
  272.     c4_Storage storage (dest, true);
  273.  
  274.         // this scans the ftp site and saves the results
  275.     c4_View view = fScanFTP(arg, id, pass)